import { HTMLAttributes, PropsWithChildren, ReactHTML, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import React from 'react'
import { createPortal } from 'react-dom'
import { useLatest } from 'ahooks'
import { omit, pick } from 'lodash'
import classNames from 'classnames'
import { ConfigProvider, Popover, PopoverProps, Tooltip } from 'antd'

import { findClosestScrollContainer } from '@/utils/findClosestScrollContainer'
import styles from './styles.module.less'

const popoverPickKeys = [
  'content',
  'align',
  'arrow',
  'autoAdjustOverflow',
  'getPopupContainer',
  'mouseEnterDelay',
  'mouseLeaveDelay',
  'overlayClassName',
  'overlayStyle',
  'overlayInnerStyle',
  'placement',
  'zIndex',
] as const

interface TextyProps
  extends Omit<HTMLAttributes<HTMLElement>, 'content'>,
    Pick<PopoverProps, (typeof popoverPickKeys)[number]> {
  /**
   * @default 'div'
   */
  tagName?: keyof ReactHTML
  /**
   * 最近的滚动容器（忽略 body 和 html）在滚动时关闭气泡
   * @default true
   */
  hideOnScroll?: boolean
  /**
   * 使用 Tooltip（默认 Popover）
   */
  usageTooltipComponent?: boolean
}

const staticProperties = {
  displayName: 'Texty',
  defaultProps: {
    tagName: 'div',
    hideOnScroll: true,
  },
}

export const Texty = Object.assign(
  (props: PropsWithChildren<TextyProps>) => {
    const { getPopupContainer: getContextPopupContainer } = React.useContext(ConfigProvider.ConfigContext)

    const { tagName, hideOnScroll, usageTooltipComponent, children, ...tagRest } = omit(props, popoverPickKeys)
    const { mouseEnterDelay, mouseLeaveDelay, getPopupContainer, ...popoverRest } = pick(props, popoverPickKeys)
    const Tag = tagName || 'div'

    const triggerRef = useRef<HTMLElement>(null)
    const showTimerRef = useRef<number | null>(null)
    const hideTimerRef = useRef<number | null>(null)
    const [visible, setVisible] = useState(false)

    const latestRef = useLatest({
      popoverEnterDelay: mouseEnterDelay ?? 0.3, // 与 Tooltip 默认值保持一致
      popoverLeaveDelay: mouseLeaveDelay ?? 0.1, // 与 Tooltip 默认值保持一致
      visible,
      clearTimers: () => {
        if (showTimerRef.current) {
          clearTimeout(showTimerRef.current)
          showTimerRef.current = null
        }
        if (hideTimerRef.current) {
          clearTimeout(hideTimerRef.current)
          hideTimerRef.current = null
        }
      },
      handleShow: () => {
        latestRef.current.clearTimers()
        showTimerRef.current = window.setTimeout(() => {
          showTimerRef.current = null
          latestRef.current.clearTimers()
          if (!latestRef.current.visible) {
            const trigger = triggerRef.current
            if (trigger && trigger.scrollWidth > trigger.offsetWidth) {
              setVisible(true)
            }
          }
        }, latestRef.current.popoverEnterDelay * 1000)
      },
      handleHide: (delay?: number) => {
        latestRef.current.clearTimers()
        hideTimerRef.current = window.setTimeout(
          () => {
            hideTimerRef.current = null
            latestRef.current.clearTimers()
            if (latestRef.current.visible) {
              setVisible(false)
            }
          },
          delay ?? latestRef.current.popoverLeaveDelay * 1000,
        )
      },
      getContextPopupContainer,
      getPopupContainer,
    })

    const getContainer = useCallback(
      (triggerNode: HTMLElement): HTMLElement => {
        const func = latestRef.current.getPopupContainer || latestRef.current.getContextPopupContainer
        return func ? func(triggerNode) : document.body
      },
      [latestRef],
    )

    const container = useMemo<HTMLElement | null>(() => {
      if (!visible || !triggerRef.current) return document.body
      return getContainer(triggerRef.current)
    }, [getContainer, visible])

    useEffect(() => {
      if (hideOnScroll && visible && triggerRef.current) {
        const scrollContainer = findClosestScrollContainer(triggerRef.current)
        if (scrollContainer) {
          const func = () => latestRef.current.handleHide(0)
          scrollContainer.addEventListener('scroll', func, { once: true })
          return () => scrollContainer.removeEventListener('scroll', func)
        }
      }
    }, [latestRef, hideOnScroll, visible])

    useEffect(() => () => latestRef.current.clearTimers(), [latestRef])

    const Comp = usageTooltipComponent ? Tooltip : Popover
    const content = popoverRest.content || children

    return (
      <Tag
        {...tagRest}
        {...({ ref: triggerRef } as any)}
        className={classNames(styles.Texty, tagRest.className)}
        onMouseEnter={e => {
          latestRef.current.handleShow()
          tagRest.onMouseEnter?.call(null, e)
        }}
        onMouseLeave={e => {
          latestRef.current.handleHide()
          tagRest.onMouseLeave?.call(null, e)
        }}
      >
        {children}

        {!!visible &&
          !!container &&
          createPortal(
            <Comp
              {...popoverRest}
              title={usageTooltipComponent ? content : undefined}
              content={usageTooltipComponent ? undefined : content}
              overlayClassName={classNames(styles.Texty_overlay, popoverRest.overlayClassName)}
              mouseEnterDelay={0}
              mouseLeaveDelay={latestRef.current.popoverLeaveDelay}
              open
              {...{ getTriggerDOMNode: () => triggerRef.current }} // hack
              getPopupContainer={getContainer}
            />,
            container,
          )}
      </Tag>
    )
  },

  staticProperties,
)
